package org.amphiprion.gameengine.font;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import org.amphiprion.gameengine.IHMIComponent;
import org.amphiprion.gameengine.IObject2D;
import org.amphiprion.gameengine.ScreenProperty;
import org.amphiprion.gameengine.util.ShaderHelper;
import org.amphiprion.gameengine.util.Texture;
import org.amphiprion.gameengine.util.TextureHelper;
import org.amphiprion.gameengine.util.Vector4;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.Matrix;

public class Text2D implements IObject2D, IHMIComponent {
	/** How many bytes per float. */
	private final int mBytesPerFloat = 4;

	/** Size of the texture coordinate data in elements. */
	private final int mTextureCoordinateDataSize = 2;

	private Context context;
	private boolean enable = true;

	// Our vertex buffer.
	private FloatBuffer mVerticesBuffer = null;

	// Our UV texture buffer.
	private FloatBuffer mTextureBuffer; // New variable.

	// The bitmap we want to load as a texture.
	protected String uri; // New variable.
	protected Texture texture; // New variable.

	private int nbVertex;
	private IObject2D parent;

	public float rotationDegree; // degree

	// Smooth Colors
	private float[] mColorBuffer = null;
	private boolean visible = true;
	// Translate params.
	public int x = 0;
	public int y = 0;
	private int width;
	private int height;
	public Vector4 maskColor = new Vector4(1, 1, 1, 1);

	public Alignment alignment = Alignment.CENTER;

	private float scale = 1.0f;
	private Font font;
	private String text;
	private float totalAdvanceWidth;
	public Object tagObject;

	public enum Alignment {
		CENTER, LEFT, RIGHT
	}

	public Text2D(Context context, Font font) {
		this.context = context;
		this.uri = font.uri;
		this.font = font;
		// setVertices(vertices);
		// setTextureCoordinates(textureCoordinates);
	}

	int program = -1;
	private Vector4 tmpMaskColor = new Vector4(1, 1, 1, 1);

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
		totalAdvanceWidth = font.getTotalAdvanceWidth(getText()) * scale;
	}

	@Override
	public void draw(float[] mMVPMatrix, float[] mViewMatrix, float[] mModelMatrix, float[] mProjectionMatrix, ScreenProperty sp) {
		String txtValue = getText();

		if (txtValue == null) {
			return;
		}

		float screenScaleX = sp.screenScaleX;
		float screenScaleY = sp.screenScaleY;
		int screenWidth = (int) sp.realWidth;
		int screenHeight = (int) sp.realHeight;

		// Set flat color
		if (!enable) {
			// gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
			// GL10.GL_MODULATE);
			tmpMaskColor.X = 0.4f * sp.maskColorRed * maskColor.X;
			tmpMaskColor.Y = 0.4f * sp.maskColorGreen * maskColor.Y;
			tmpMaskColor.Z = 0.4f * sp.maskColorBlue * maskColor.Z;
			tmpMaskColor.W = sp.maskColorAlpha * maskColor.W;

			// GL2.Color4 (0.4f * sp.maskColorRed, 0.4f * sp.maskColorGreen, 0.4f * sp.maskColorBlue, 1.0f * sp.maskColorAlpha);
		} else {
			tmpMaskColor.X = sp.maskColorRed * maskColor.X;
			tmpMaskColor.Y = sp.maskColorGreen * maskColor.Y;
			tmpMaskColor.Z = sp.maskColorBlue * maskColor.Z;
			tmpMaskColor.W = sp.maskColorAlpha * maskColor.W;
			// GL2.Color4 (sp.maskColorRed, sp.maskColorGreen, sp.maskColorBlue, sp.maskColorAlpha);
		}
		//
		// GL2.FrontFace (All2.Ccw);
		// // Enable face culling.
		// GL2.Enable (All2.CullFace);
		// // What faces to remove with the face culling.
		// GL2.CullFace (All2.Back);
		// New part...
		if (uri != null && (texture == null || !texture.loaded)) {
			loadGLTexture();
		}

		// ////////////////////////////////////
		// ////////////////////////////////////
		// ////////////////////////////////////
		float offsetX = 0;
		if (alignment == Alignment.CENTER) {
			offsetX = -totalAdvanceWidth / 2;
		} else if (alignment == Alignment.RIGHT) {
			offsetX = -totalAdvanceWidth;
		} else {
			offsetX = 0;
		}
		float offsetY = -font.lineHeight * scale / 2;

		if (program == -1) {
			program = ShaderHelper.getProgramHandle("IMAGE2D");
		}
		GLES20.glUseProgram(program);
		int mMVPMatrixHandle = GLES20.glGetUniformLocation(program, "u_MVPMatrix");
		int mPositionHandle = GLES20.glGetAttribLocation(program, "a_Position");
		int mColorHandle = GLES20.glGetUniformLocation(program, "color");
		int mTextureUniformHandle = GLES20.glGetUniformLocation(program, "texture1");
		int mTextureCoordinateHandle = GLES20.glGetAttribLocation(program, "aTextureCoord");

		int stepX = 1;
		int stepY = 0;
		int stepDescWith = 1;
		int stepDescOffX = 1;
		float stepDescHeight = 1;
		float stepDescOffY = 1;
		if (rotationDegree == 180) {
			stepX = -1;
			offsetX = -offsetX;
			stepDescWith = -1;
			stepDescHeight = -1;
			stepDescOffY = -1;
			offsetY = -offsetY;
		} else if (rotationDegree == 90) {
			stepX = 0;
			stepY = -1;
			float tmpox = offsetX;
			offsetX = offsetY;
			offsetY = -tmpox + font.lineHeight * scale / 4; // tips
			// offsetX = -font.lineHeight * scale / 2;
			offsetX = -font.lineHeight * scale / 3; // tips

			stepDescWith = 1;
			stepDescOffX = 1;
			stepDescOffY = -1;
			stepDescHeight = -1;

		} else if (rotationDegree == -90 || rotationDegree == 270) {
			stepX = 0;
			stepY = 1;
			float tmpox = offsetX;
			offsetX = -offsetY;
			offsetY = tmpox - font.lineHeight * scale / 4; // tips;
			// offsetX = font.lineHeight * scale / 2;
			offsetX = font.lineHeight * scale / 3; // tips

			stepDescWith = -1;
			stepDescOffX = -1;
			stepDescOffY = 1;
			stepDescHeight = 1;
		}

		CharacterDescription cDesc;
		for (int i = 0; i < txtValue.length(); i++) {
			char c = txtValue.charAt(i);

			int asci = c;
			if (asci != 32) {
				cDesc = font.getCharacter(asci);
				// Log.d(ApplicationConstants.PACKAGE, "char:" + c + "  cx=" + cDesc.x + "  cy=" + cDesc.y + "  cw=" + cDesc.width + "  ch=" + cDesc.height + "  font.coordWith=" + font.coordWith + " font.coordHeight=" + font.coordHeight);
				// 0
				mVerticesBuffer.put(0, -cDesc.width / 2.0f);
				mVerticesBuffer.put(1, cDesc.height / 2.0f);
				mVerticesBuffer.put(2, 0);
				// 1
				mVerticesBuffer.put(3, -cDesc.width / 2.0f);
				mVerticesBuffer.put(4, -cDesc.height / 2.0f);
				mVerticesBuffer.put(5, 0);
				// 2
				mVerticesBuffer.put(6, cDesc.width / 2.0f);
				mVerticesBuffer.put(7, -cDesc.height / 2.0f);
				mVerticesBuffer.put(8, 0);
				// 0
				mVerticesBuffer.put(9, -cDesc.width / 2.0f);
				mVerticesBuffer.put(10, cDesc.height / 2.0f);
				mVerticesBuffer.put(11, 0);
				// 2
				mVerticesBuffer.put(12, cDesc.width / 2.0f);
				mVerticesBuffer.put(13, -cDesc.height / 2.0f);
				mVerticesBuffer.put(14, 0);
				// 3
				mVerticesBuffer.put(15, cDesc.width / 2.0f);
				mVerticesBuffer.put(16, cDesc.height / 2.0f);
				mVerticesBuffer.put(17, 0);

				// 0
				mTextureBuffer.put(0, cDesc.x / (float) font.coordWith);
				mTextureBuffer.put(1, cDesc.y / (float) font.coordHeight);
				// 1
				mTextureBuffer.put(2, cDesc.x / (float) font.coordWith);
				mTextureBuffer.put(3, (cDesc.y + cDesc.height - 1) / (float) font.coordHeight);
				// 2
				mTextureBuffer.put(4, (cDesc.x + cDesc.width - 1) / (float) font.coordWith);
				mTextureBuffer.put(5, (cDesc.y + cDesc.height - 1) / (float) font.coordHeight);
				// 0
				mTextureBuffer.put(6, cDesc.x / (float) font.coordWith);
				mTextureBuffer.put(7, cDesc.y / (float) font.coordHeight);
				// 2
				mTextureBuffer.put(8, (cDesc.x + cDesc.width - 1) / (float) font.coordWith);
				mTextureBuffer.put(9, (cDesc.y + cDesc.height - 1) / (float) font.coordHeight);
				// 3
				mTextureBuffer.put(10, (cDesc.x + cDesc.width - 1) / (float) font.coordWith);
				mTextureBuffer.put(11, cDesc.y / (float) font.coordHeight);

				Matrix.setIdentityM(mModelMatrix, 0);
				Matrix.translateM(mModelMatrix, 0, (x + offsetX + stepDescWith * cDesc.width * scale / 2 + stepDescOffX * cDesc.xOffset * scale) * screenScaleX, -(y - sp.referenceHeight + stepDescOffY * cDesc.yOffset * scale + offsetY + stepDescHeight * cDesc.height * scale / 2) * screenScaleY, 0);
				Matrix.scaleM(mModelMatrix, 0, scale * screenScaleX, scale * screenScaleY, 1);
				Matrix.rotateM(mModelMatrix, 0, rotationDegree, 0.0f, 0.0f, 1.0f);
				// This multiplies the view matrix by the model matrix, and stores the result in the MVP matrix
				// (which currently contains model * view).
				// Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
				// This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
				// (which now contains model * view * projection).
				// Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
				Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mModelMatrix, 0);

				// Pass in the combined matrix.
				GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);

				// the vertex coordinates
				mVerticesBuffer.position(0);
				GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, mVerticesBuffer);

				GLES20.glEnableVertexAttribArray(0);

				// the normal info
				// GL2.VertexAttribPointer(BasicShader.ATTRIB_NORMAL_COORD_INDEX, 3, All2.Float, false, 0, mNormalBuffer);
				// GL2.EnableVertexAttribArray(BasicShader.ATTRIB_NORMAL_COORD_INDEX);

				// GL2.Uniform1(Image2DShader.UNIFORM_COLOR_LOCATION, 0);
				GLES20.glUniform4fv(mColorHandle, 1, tmpMaskColor.getArray(), 0);

				// Texture info
				// bind textures
				if (texture != null) {// && enableTexture) {
					// TODO rejouter un parameter dans le shader pour dire si y a ou pas une texture

					GLES20.glActiveTexture(GLES20.GL_TEXTURE);
					GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture.textureId);
					GLES20.glUniform1i(mTextureUniformHandle, 0);

					// texture coordinates
					mTextureBuffer.position(0);
					GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false, 0, mTextureBuffer);

					// GLES20.glEnableVertexAttribArray(2);
				}

				GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, nbVertex);
				offsetX += stepX * cDesc.xAdvance * scale;
				offsetY += stepY * cDesc.xAdvance * scale;
			} else {
				cDesc = font.getCharacter(32);
				offsetX += stepX * cDesc.xAdvance * scale;
				offsetY += stepY * cDesc.xAdvance * scale;
			}
		}

	}

	/**
	 * Set the vertices.
	 * 
	 * @param vertices
	 */
	protected void setVertices(float[] vertices) {
		nbVertex = vertices.length / 3;
		// a float is 4 bytes, therefore we multiply the number if
		// vertices with 4.
		// ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
		// vbb.order(ByteOrder.nativeOrder());
		// mVerticesBuffer = vbb.asFloatBuffer();
		// mVerticesBuffer.put(vertices);
		// mVerticesBuffer.position(0);
		mVerticesBuffer = ByteBuffer.allocateDirect(vertices.length * mBytesPerFloat).order(ByteOrder.nativeOrder()).asFloatBuffer();
		mVerticesBuffer.put(vertices).position(0);

	}

	/**
	 * Set the texture coordinates.
	 * 
	 * @param textureCoords
	 */
	protected void setTextureCoordinates(float[] textureCoords) { // New
																	// function.
																	// float is 4 bytes, therefore we multiply the number if
																	// vertices with 4.
																	// ByteBuffer byteBuf = ByteBuffer.allocateDirect(textureCoords.length * 4);
		// byteBuf.order(ByteOrder.nativeOrder());
		// mTextureBuffer = byteBuf.asFloatBuffer();
		// mTextureBuffer.put(textureCoords);
		// mTextureBuffer.position(0);
		mTextureBuffer = ByteBuffer.allocateDirect(textureCoords.length * mBytesPerFloat).order(ByteOrder.nativeOrder()).asFloatBuffer();
		mTextureBuffer.put(textureCoords).position(0);
	}

	/**
	 * Set the colors
	 * 
	 * @param colors
	 */
	protected void setColors(float[] colors) {
		// float has 4 bytes.
		// ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
		// cbb.order(ByteOrder.nativeOrder());
		// mColorBuffer = cbb.asFloatBuffer();
		// mColorBuffer.put(colors);
		// mColorBuffer.position(0);
		mColorBuffer = colors;
	}

	/**
	 * Loads the texture.
	 * 
	 * @param gl
	 */
	public void loadGLTexture() { // New function
		texture = TextureHelper.loadTexture(context, uri);
		// Log.d(ApplicationConstants.PACKAGE, "@@@@@@@@@@@@ texture.originalWidth=" + texture.originalWidth + "     texture.originalHeight=" + texture.originalHeight);
		// float[] v = {
		// // X, Y
		// 0, 0, 0.0f, 0, (texture.originalHeight - 1) , 0.0f, (texture.originalWidth - 1) , (texture.originalHeight - 1) , 0.0f,
		// (texture.originalWidth - 1) , 0, 0.0f };
		float[] v = { -texture.originalWidth / 2, texture.originalHeight / 2, 0.0f, // 0, Top Left
				-texture.originalWidth / 2, -texture.originalHeight / 2, 0.0f, // 1, Bottom Left
				texture.originalWidth / 2, -texture.originalHeight / 2, 0.0f, // 2, Bottom Right
				-texture.originalWidth / 2, texture.originalHeight / 2, 0.0f, // 0, Top Left
				texture.originalWidth / 2, -texture.originalHeight / 2, 0.0f, // 2, Bottom Right
				texture.originalWidth / 2, texture.originalHeight / 2, 0.0f, // 3, Top Right
		};
		float[] t = { 0.0f, 0.0f, // 0,top left
				0.0f, (float) texture.originalHeight / texture.height, // 1,bottom left
				(float) texture.originalWidth / texture.width, (float) texture.originalHeight / texture.height, // 2,bottom right
				0.0f, 0.0f, // 0,top left
				(float) texture.originalWidth / texture.width, (float) texture.originalHeight / texture.height, // 2,bottom right
				(float) texture.originalWidth / texture.width, 0.0f, // 3,top right
		};
		width = texture.originalWidth;
		height = texture.originalHeight;
		setTextureCoordinates(t);
		setVertices(v);
	}

	public void setUri(String uri) {
		texture = null;
		this.uri = uri;
	}

	@Override
	public void setParent(IObject2D parent) {
		this.parent = parent;
	}

	@Override
	public IObject2D getParent() {
		return parent;
	}

	@Override
	public void removeObject(IObject2D obj) {
		// TODO Auto-generated method stub

	}

	@Override
	public int getX() {
		return x;
	}

	@Override
	public void setX(int x) {
		this.x = x;
	}

	@Override
	public int getY() {
		return y;
	}

	@Override
	public void setY(int y) {
		this.y = y;
	}

	@Override
	public boolean isEnable() {
		return enable;
	}

	@Override
	public float getScale() {
		return scale;
	}

	@Override
	public void setScale(float scale) {
		this.scale = scale;
	}

	@Override
	public boolean contains(int ptx, int pty) {
		return contains(ptx, pty, 0);
	}

	@Override
	public boolean contains(int ptx, int pty, int tolerance) {
		int px = ptx - x;
		int py = pty - y;
		if (rotationDegree != 0) {
			double rotation = Math.PI * rotationDegree / 180;
			int npx = (int) (px * Math.cos(rotation) - py * Math.sin(rotation));
			int npy = (int) (px * Math.sin(rotation) + py * Math.cos(rotation));
			px = npx;
			py = npy;
		}
		if (px + tolerance < -texture.originalWidth / 2) {
			return false;
		}
		if (px - tolerance > +texture.originalWidth / 2) {
			return false;
		}
		if (py + tolerance < -texture.originalHeight / 2) {
			return false;
		}
		if (py - tolerance > +texture.originalHeight / 2) {
			return false;
		}
		return true;
	}

	@Override
	public void setGlobalScale(float globalScale) {
		this.scale = globalScale;
	}

	@Override
	public void setEnable(boolean enable) {
		this.enable = enable;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	@Override
	public boolean isVisible() {
		return visible;
	}

	@Override
	public void setVisible(boolean visible) {
		this.visible = visible;
	}
}
